
/* GCSx
** SCENE.CPP
**
** Scene support, entities and drawing
** Non-EDITOR members
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

Scene::Scene(World* myWorld, int myId) : name(blankString), nameL(blankString) { start_func
    if (myId) assert(myWorld);

    cached = 0;
    lockCount = 0;
    cacheFile = NULL;
    world = myWorld;
    id = myId;
    
    for (int pos = 0; pos < MAX_LAYERS; ++pos) {
        layers[pos] = NULL;
    }
    layerCount = 0;
    
    vpX = 0;
    vpY = 0;
}

Scene::~Scene() { start_func
    for (int pos = 0; pos < layerCount; ++pos) {
        delete layers[pos];
    }
    delete cacheFile;
}

int Scene::findLayer(const string& fName) const { start_func
    for (int pos = 0; pos < layerCount; ++pos) {
        assert(layers[pos]);
        if (layers[pos]->getNameL() == fName) return pos;
    }
    return -1;
}

int Scene::findLayer(const Layer* fLayer) const { start_func
    for (int pos = 0; pos < layerCount; ++pos) {
        assert(layers[pos]);
        if (layers[pos] == fLayer) return pos;
    }
    return -1;
}
 
const Layer* Scene::getLayer(int pos) const { start_func
    assert(pos >= 0);
    assert(pos < layerCount);
    
    return layers[pos];
}

Layer* Scene::getLayer(int pos) { start_func
    assert(pos >= 0);
    assert(pos < layerCount);
    
    return layers[pos];
}
   
void Scene::cacheLoad() throw_File { start_func
    if (cached) {
        // load into memory
        assert(cacheFile);
        assert(cacheFile->getVersion() <= 1);
        
        cacheFile->rewind();

        for (int pos = 0; pos < layerCount; ++pos) {
            assert(layers[pos]);
            try {
                layers[pos]->loadContent(cacheFile);
            }
            catch (...) {
                for (--pos; pos >= 0; --pos) {
                    layers[pos]->recacheContent();
                }
                throw;
            }
        }
        
        // We retain this pointer and reuse it IF we
        // unlock to 0 AND aren't modified
        cached = 0;
    }
}

void Scene::loadHeader(FileRead* file) throw_File { start_func
    assert(world);
    
    if (file->getVersion() > 1) {
        throw FileException("Unsupported scene version %d", file->getVersion());
    }

    file->readStr(name);
    nameL = name;
    toLower(nameL);
    layerCount = file->readInt();
    id = file->readInt();

    for (int pos = 0; pos < layerCount; ++pos) {
        layers[pos] = new Layer(world, this);
        layers[pos]->loadHeader(file);
    }
    
    // Check validity
    if ((layerCount > MAX_LAYERS) || (layerCount < 0) || (!id)) {
        throw FileException("Corrupted scene header");
    }
}

void Scene::loadContent(FileRead* file) { start_func
    delete cacheFile;
    cacheFile = file;
    cached = 1;
}

int Scene::isContentCached() const { start_func
    return cached;
}

int Scene::doLock(int play) throw_File { start_func
    cacheLoad();
    for (int pos = 0; pos < layerCount; ++pos) {
        assert(layers[pos]);
        // @TODO: throw_File
        if (play) layers[pos]->markLockPlay();
        else layers[pos]->markLock();
    }
    if (!lockCount) {
        EntityOrder::iterator end = entitysByOrder.end();
        for (EntityOrder::iterator pos = entitysByOrder.begin(); pos != end; ++pos) {
            (*pos)->setActive();
        }
    }
    return ++lockCount;
}

int Scene::doUnlock(int play) throw_File { start_func
    --lockCount;
    assert(lockCount >= 0);
    if (!lockCount) {
        EntityOrder::iterator end = entitysByOrder.end();
        for (EntityOrder::iterator pos = entitysByOrder.begin(); pos != end; ++pos) {
            (*pos)->setInactive();
        }
    }
    for (int pos = 0; pos < layerCount; ++pos) {
        assert(layers[pos]);
        if (play) layers[pos]->markUnlockPlay();
        else layers[pos]->markUnlock();
        // Recache ourselves
        if ((lockCount == 0) && (cacheFile)) layers[pos]->recacheContent();
    }
    if ((lockCount == 0) && (cacheFile)) {
        // Recache ourselves
        cached = 1;
    }
    return lockCount;
}

int Scene::markLock() throw_File { start_func
    return doLock(0);
}

int Scene::markUnlock() { start_func
    return doUnlock(0);
}

int Scene::markLockPlay() throw_File { start_func
    return doLock(1);
}

int Scene::markUnlockPlay() { start_func
    return doUnlock(1);
}

int Scene::isLocked() const { start_func
    return lockCount;
}

void Scene::draw() { start_func
    for (int pos = layerCount - 1; pos >= 0; --pos) {
        assert(layers[pos]);
        layers[pos]->draw(vpX, vpY);
    }
}

void Scene::spawnScene() { start_func
    for (int pos = 0; pos < layerCount; ++pos) {
        assert(layers[pos]);
        layers[pos]->spawnLayer();
    }
}

void Scene::indexEntity(Entity* addEntity) { start_func
    assert(addEntity);
    WorldPlay* wp = dynamic_cast<WorldPlay*>(world);
    assert(wp);
    
    wp->indexEntity(addEntity);
    orderEntity(addEntity);
    nameEntity(addEntity);
    if (lockCount) addEntity->setActive();
}

void Scene::deindexEntity(Entity* remEntity) { start_func
    assert(remEntity);
    WorldPlay* wp = dynamic_cast<WorldPlay*>(world);
    assert(wp);
    
    if (lockCount) remEntity->setInactive();
    wp->deindexEntity(remEntity);
    deorderEntity(remEntity);
    denameEntity(remEntity);
}

void Scene::orderEntity(Entity* addEntity) { start_func
    assert(addEntity);
    assert(entitysByOrder.find(addEntity) == entitysByOrder.end());

    entitysByOrder.insert(addEntity);
}

void Scene::deorderEntity(Entity* remEntity) { start_func
    assert(remEntity);
    entitysByOrder.erase(remEntity);
}

void Scene::nameEntity(Entity* addEntity) { start_func
    assert(addEntity);
    entitysByName.insert(pair<const char*, Entity*>(addEntity->getName()->c_str(), addEntity));
}

void Scene::denameEntity(Entity* remEntity) { start_func
    assert(remEntity);
    // Have to search range for matching entity
    pair<EntityMap::iterator, EntityMap::iterator> range = entitysByName.equal_range(remEntity->getName()->c_str());
    for (EntityMap::iterator pos = range.first; pos != range.second; ++pos) {
        if ((*pos).second == remEntity) {
            entitysByName.erase(pos);
            return;
        }
    }
}

void Scene::matchEntity(const char* fMatch, EntityMap::const_iterator& first, EntityMap::const_iterator& last) const { start_func
    assert(fMatch);
    // Optimization one- * or *something
    if (fMatch[0] == '*') {
        first = entitysByName.begin();
        last = entitysByName.end();
        return;
    }
    
    // Search for position of first *
    string toMatch = fMatch;
    string::size_type pos = toMatch.find('*');
    
    // Optimization two- exact match
    if (pos == string::npos) {
        pair<EntityMap::const_iterator, EntityMap::const_iterator> range = entitysByName.equal_range(toMatch.c_str());
        first = range.first;
        last = range.second;
        return;
    }
    
    // We need to determine range
    // First in range is portion before wildcard
    // Last in range is portion before wildcard with last character lexically incremented
    assert(pos > 0);
    toMatch = toMatch.substr(0, pos);
    first = entitysByName.lower_bound(toMatch.c_str());
    ++toMatch[pos - 1];
    last = entitysByName.lower_bound(toMatch.c_str());
}

void Scene::cycle() { start_func
    EntityOrder::iterator end = entitysByOrder.end();
    for (EntityOrder::iterator pos = entitysByOrder.begin(); pos != end; ++pos) {
        (*pos)->cycle();
    }
}
